﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using ControleRepresentante.Common;
using ControleRepresentante.Logic.Models;
using ControleRepresentante.Logic.Statistics;
using NHibernate;
using NHibernate.Criterion;

namespace ControleRepresentante.Logic.Repositories
{
    public interface IUserRepository : IRepository<User, StatisticsBase>
    {
        User GetUser(string username, string password, out Representante representante);
        void AddUser(User user);
        IEnumerable<User> GetAllModels();
        void UpdateModel(User model, bool checkForRepresentante);
    }

    public enum UserError
    {
        DuplicatedUsername,
        UserAndRepresentanteMismatch
    }

    public class UserRepository : GeneralRepositoryBase<User, StatisticsBase, UserError>, IUserRepository
    {
        public UserRepository(IRepositoryConfiguration repositoryConfiguration, ISession session) 
            : base(repositoryConfiguration, session)
        {
        }

        public User GetUser(string username, string password, out Representante representante)
        {
            User user = Session.QueryOver<User>()
                .Where(x => x.Username == username)
                .And(x => x.Password == password.Encrypt())
                .SingleOrDefault();
            representante = user == null 
                ? null 
                : user.Representante;
            return user;
        }

        protected override void OnBeforeRemove(User model)
        {
            if (model.Representante.Id != GetRepresentanteId())
            {
                ThrowRepositoryException(UserError.UserAndRepresentanteMismatch);
            }
            GetRepresentante().RemoveUser(model);
            model.ClearPosts();
            model.ClearPermissoes();
        }

        protected override IQueryOver<User, User> GetModelsQueryOver()
        {
            return Session.QueryOver<User>()
                .Where(x => x.Representante.Id == GetRepresentanteId())
                .OrderBy(x => x.Username).Asc;
        }

        protected override IEnumerable<Expression<Func<User, bool>>> GetUniquenessExpressions(User model)
        {
            yield return x => x.Username == model.Username;
        }

        protected override IQueryOver<User, User> GetModelByIdQueryOver(IQueryOver<User, User> queryOver, Guid id)
        {
            return queryOver.And(x => x.Representante.Id == GetRepresentanteId());
        }

        protected override UserError DuplicatedError { get { return UserError.DuplicatedUsername; } }

        protected override StatisticsBase GetStatistics(int modelCount)
        {
            return new StatisticsBase(modelCount);
        }

        protected override void OnBeforeSave(User model)
        {
            base.OnBeforeSave(model);
            model.IsAdmin = false;
            Representante representante = GetRepresentante();
            representante.AddUser(model);
            model.Representante = representante;
        }

        public void UpdateModel(User model, bool checkForRepresentante)
        {

            if (checkForRepresentante)
            {
                EnsureIsRepresentante();
            }
            else
            {
                EnsureIsAdmin();
            }

            VerifyUniqueness(model);

            using (ITransaction transaction = Session.BeginTransaction())
            {
                Session.Update(model);
                transaction.Commit();
            }
        }

        public void AddUser(User user)
        {
            using (ITransaction transaction = Session.BeginTransaction())
            {
                Session.Save(user);
                transaction.Commit();
            }
        }

        public IEnumerable<User> GetAllModels()
        {
            return Session.QueryOver<User>()
                .OrderBy(x => x.Username).Asc
                .Future();
        }

        public override IEnumerable<User> GetModelsByIds(IEnumerable<Guid> ids)
        {
            return Session.QueryOver<User>()
                .Where(x => x.Representante.Id == GetRepresentanteId())
                .And(x => x.Id.IsIn(ids.ToArray()))
                .List();
        }
    }
}